﻿/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Jurgen Van Gael
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

using System;
using dnAnalytics.Properties;

namespace dnAnalytics.Statistics.Distributions
{
    /// <summary>
    /// The exponential distribution is a distribution over the real numbers parameterized by one non-negative parameter.
    /// </summary>
    /// <remarks>The distribution will use the <see cref="System.Random"/> by default. 
    /// Users can set the random number generator by using the <see cref="RandomNumberGenerator"/> property.
    /// 
    /// The statistics classes will check all the incoming parameters whether they are in the allowed
    /// range. This might involve heavy computation. Optionally, by setting Control.CheckDistributionParameters
    /// to false, all parameter checks can be turned off.</remarks>
    public class Exponential : IContinuousDistribution
    {
        // Exponential distribution parameters.
        private readonly double mLambda;

        /// <summary>
        /// Initializes a new instance of the <see cref="Exponential"/> class.
        /// </summary>
        /// <param name="lambda">The lambda parameter.</param>
        public Exponential(double lambda)
        {
            if (Control.CheckDistributionParameters)
            {
                CheckParameters(lambda);
            }
            
            mLambda = lambda;
            RandomNumberGenerator = new System.Random();
        }

        #region IDistribution Members

        /// <summary>
        /// The mean of the distribution.
        /// </summary>
        /// <value></value>
        public double Mean
        {
            get { return 1.0/mLambda; }
        }

        /// <summary>
        /// The standard deviation of the distribution.
        /// </summary>
        /// <value></value>
        public double StdDev
        {
            get { return 1.0/mLambda; }
        }

        /// <summary>
        /// The variance of the distribution.
        /// </summary>
        /// <value></value>
        public double Variance
        {
            get { return 1.0/(mLambda*mLambda); }
        }

        /// <summary>
        /// The entropy of the distribution.
        /// </summary>
        /// <value></value>
        public double Entropy
        {
            get { return 1.0 - System.Math.Log(mLambda); }
        }

        /// <summary>
        /// Gets or sets the random number generator.
        /// </summary>
        /// <value>The random number generator used to generate a random sample.</value>
        public System.Random RandomNumberGenerator { get; set; }

        #endregion

        #region IContinuousDistribution Members

        /// <summary>
        /// The mode of the distribution.
        /// </summary>
        /// <value></value>
        public double Mode
        {
            get { return 0.0; }
        }

        /// <summary>
        /// The median of the distribution.
        /// </summary>
        /// <value></value>
        public double Median
        {
            get { return System.Math.Log(2.0) / mLambda; }
        }

        /// <summary>
        /// Computes values of the probability density function.
        /// </summary>
        /// <param name="x">The location in the domain where we want to evaluate the probability density function.</param>
        /// <returns></returns>
        public double Density(double x)
        {
            if (x >= 0.0)
            {
                return mLambda * System.Math.Exp(-mLambda * x);
            }
            return 0.0;
        }

        /// <summary>
        /// Samples an exponentially distributed random variable.
        /// </summary>
        public double Sample()
        {
            return DoSample(RandomNumberGenerator, mLambda);
        }

        /// <summary>
        /// Samples draws a random sample for the distribution.
        /// </summary>
        /// <param name="size">The size of the sample.</param>
        /// <returns>
        /// An array of random numbers from the distribution.
        /// </returns>
        public double[] Sample(int size)
        {
            return DoSample(RandomNumberGenerator, size, mLambda);
        }

        #endregion

        /// <summary>
        /// Evaluates the cumulative distribution function for the exponential distribution.
        /// </summary>
        public double CumulativeDistribution(double x)
        {
            if (x >= 0.0)
            {
                return 1.0 - System.Math.Exp(-mLambda*x);
            }
            return 0.0;
        }

        /// <summary>
        /// Samples an exponentially distributed random variable.
        /// </summary>
        /// <param name="rnd">The random number generator to use.</param>
        /// <param name="lambda">The exponential distribution parameter.</param>
        public static double Sample(System.Random rnd, double lambda)
        {
            if (Control.CheckDistributionParameters)
            {
                CheckParameters(lambda);
            }
            
            return DoSample(rnd, lambda);
        }


        /// <summary>
        /// Samples an exponentially distributed random variable.
        /// </summary>
        /// <param name="rnd">The random number generator to use.</param>
        /// <param name="size">The size of the sample to draw.</param>
        /// <param name="lambda">The exponential distribution parameter.</param>
        public static double[] Sample(System.Random rnd, int size, double lambda)
        {
            if (Control.CheckDistributionParameters)
            {
                CheckParameters(lambda);
            }
            
            return DoSample(rnd, size, lambda);
        }

        private static void CheckParameters(double lambda)
        {
            if (lambda < 0.0)
            {
                throw new ArgumentOutOfRangeException("lambda", Resources.ParameterCannotBeNegative);
            }
        }

        private static double DoSample(System.Random rnd, double lambda)
        {
            double r = rnd.NextDouble();
            while (r == 0.0)
            {
                r = rnd.NextDouble();
            }

            return -System.Math.Log(r)/lambda;
        }


        private static double[] DoSample(System.Random rnd, int size, double lambda)
        {
            double[] sample = new double[size];
            for (int i = 0; i < sample.Length; i++)
            {
                sample[i] = DoSample(rnd, lambda);
            }
            return sample;
        }
    }
}